﻿Imports System.Windows.Forms

Public Class CodeBox : Inherits ScrollableControl

    Public Lines As New List(Of Line)

    Dim LineHeight As Integer = 16
    Dim WithEvents CursorTimer As New System.Windows.Forms.Timer With {.Interval = 200}

    Public Shared Keywords As New List(Of String)

    Sub New()
        InitializeComponent()
        FixedArea = New Size(40, 0)
        BackColor = Color.White
        DoubleBuffered = True
        ResizeRedraw = True
        Cursor = Windows.Forms.Cursors.IBeam
        Lines.Add(New Line(""))
        SelStart = New CursorPoint With {.Parent = Me}
        SelEnd = New CursorPoint With {.Parent = Me}
        Vb()
    End Sub

    Property TopLine As Integer
        Get
            Return Int(VScroll.Value / LineHeight)
        End Get
        Set(ByVal Value As Integer)
            If TopLine = Value Then Return
            VScroll.Value = Value * LineHeight
            Invalidate()
        End Set
    End Property

    ReadOnly Property BottomLine As Integer
        Get
            Return Int((VScroll.Value + Height) / LineHeight)
        End Get
    End Property

    Property SelStart As CursorPoint
    Property SelEnd As CursorPoint

    Dim _CursorOn As Boolean
    Private Property CursorOn As Boolean
        Get
            Return _CursorOn
        End Get
        Set(ByVal Value As Boolean)
            If CursorOn = Value Then Return
            _CursorOn = Value
            SelEnd.InvalidateLine()
        End Set
    End Property

    Private ReadOnly Property VisibleLineCount As Integer
        Get
            Return Int(ClientRectangle.Height / LineHeight)
        End Get
    End Property

    ReadOnly Property ActiveLine As Line
        Get
            Return Lines(SelEnd.Line)
        End Get
    End Property

    Sub EnsureCursorVisible()
        Dim R = LineRect(SelEnd.Line)
        Dim P = TopLeft
        R.Offset(-P.X, -P.Y)
        If R.Top < ClientRectangle.Top Then P.Y = LineHeight * SelEnd.Line
        If R.Bottom >= ClientRectangle.Bottom Then P.Y = LineHeight * (SelEnd.Line + 1) - ClientRectangle.Height
        Dim x = TextWidth(CreateGraphics, Mid(ActiveLine.Text, 1, SelEnd.Pos), Font) - HScroll.Value
        If x < 0 Then P.X = x + HScroll.Value - 64
        If P.X < 0 Then P.X = 0
        If x >= ClientRectangle.Width - 1 Then P.X = x + 64 - ClientRectangle.Width + HScroll.Value
        If Not P = TopLeft Then TopLeft = P
        CursorOn = True
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        MyBase.OnPaint(e)
        Dim y As Integer = FixedArea.Height
        Dim ClipRectangle = e.ClipRectangle
        ClipRectangle.Offset(HScroll.Value, VScroll.Value)
        'e.Graphics.FillRectangle(Color.FromArgb(Rnd(1) * 255, Rnd(1) * 255, 0).Brush, Rr)
        For i = TopLine To Math.Min(BottomLine, Lines.Count - 1)
            DrawLine(e.Graphics, i, Lines(i), LineRect(i), y, ClipRectangle)
        Next
        e.Graphics.ResetTransform()
        Dim Margin As New Rectangle(0, ClientRectangle.Top, ClientRectangle.Left - 1, ClientRectangle.Height)
        e.Graphics.FillRectangle(SystemBrushes.Control, Margin)
        Dim R As New Rectangle(0, -(VScroll.Value Mod LineHeight) + ClientRectangle.Top, ClientRectangle.Left, LineHeight)
        For i = TopLine To BottomLine + 1
            If R.IntersectsWith(e.ClipRectangle) Then
                e.Graphics.FillRectangle(SystemBrushes.Control, R)
                e.Graphics.DrawText(Trim(i + 1), Font, R, SystemColors.ButtonShadow, "")
            End If
            R.Offset(0, LineHeight)
        Next
        e.Graphics.DrawLine(SystemPens.ControlDark, Margin.Right, Margin.Top, Margin.Right, Margin.Bottom)
    End Sub

    Dim Sel(1) As Point

    Private Sub DrawLine(ByVal g As Graphics, ByVal i As Integer, ByVal Line As Line, ByVal R As Rectangle, ByRef y As Integer, ByVal ClipRectangle As Rectangle)
        If Not R.IntersectsWith(ClipRectangle) Then Exit Sub
        If SelEnd.Line = i Then
            g.FillRectangle(SystemColors.Control.Opacity(255).Brush, R)
        End If
        Dim t = Lines(i).Text
        If (i > Sel(0).Y And i < Sel(1).Y) OrElse (i < Sel(0).Y And i > Sel(1).Y) Then
            Dim CP = TextWidth(g, t & " ", Font)
            g.FillRectangle(SystemColors.Highlight.Opacity(128).Brush, R.Left, R.Top, CP, R.Height)
        ElseIf (i = Sel(0).Y And i < Sel(1).Y) Then
            Dim CP1 = TextWidth(g, Mid(t, 1, Sel(0).X), Font)
            Dim CP2 = TextWidth(g, t & " ", Font)
            g.FillRectangle(SystemColors.Highlight.Opacity(128).Brush, R.Left + CP1, R.Top, CP2 - CP1, R.Height)
        ElseIf (i = Sel(1).Y And i > Sel(0).Y) Then
            Dim CP1 = TextWidth(g, Mid(t, 1, Sel(1).X), Font)
            g.FillRectangle(SystemColors.Highlight.Opacity(128).Brush, R.Left, R.Top, CP1, R.Height)
        ElseIf (i = Sel(0).Y And i = Sel(1).Y And Sel(0).X < Sel(1).X) Then
            Dim CP1 = TextWidth(g, Mid(t, 1, Sel(0).X), Font)
            Dim CP2 = TextWidth(g, Mid(t, 1, Sel(1).X), Font)
            g.FillRectangle(SystemColors.Highlight.Opacity(128).Brush, R.Left + CP1, R.Top, CP2 - CP1, R.Height)
        End If
        Line.Draw(g, R, Font)
        If SelEnd.Line = i And CursorOn Then
            Dim CP = TextWidth(g, Mid(Lines(i).Text, 1, SelEnd.Pos), Font)
            g.DrawLine(Pens.Black, R.Left + CP, R.Top, R.Left + CP, R.Bottom - 1)
        End If
        y += LineHeight
    End Sub

    ReadOnly Property LineRect(ByVal i As Integer) As Rectangle
        Get
            Return New Rectangle(ClientRectangle.Left, ClientRectangle.Top + i * LineHeight, ClientRectangle.Width + HScroll.Value - LeftMargin, LineHeight)
        End Get
    End Property

    Dim LeftMargin = 0

    Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
        MyBase.OnGotFocus(e)
        CursorOn = True
        CursorTimer.Start()
    End Sub

    Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
        MyBase.OnLostFocus(e)
        CursorTimer.Stop()
    End Sub

    Private Sub CursorTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles CursorTimer.Tick
        CursorOn.Toggle()
    End Sub

    Protected Overrides Sub OnKeyPress(ByVal e As System.Windows.Forms.KeyPressEventArgs)
        MyBase.OnKeyPress(e)
        DeleteSelection()
        Lines(SelStart.Line).Text = Mid(Lines(SelStart.Line).Text, 1, SelStart.Pos) & e.KeyChar & Mid(Lines(SelStart.Line).Text, SelStart.Pos + 1)
        MoveCursor(False, False, 0, 1)
    End Sub

    Sub MoveCursor(ByVal Extend As Boolean, ByVal Absolute As Boolean, ByVal Line As Integer, ByVal Pos As Integer)
        InvalidateSelection()
        SelEnd.MoveCursor(Absolute, Line, Pos)
        If Not Extend Then SelStart.Set(SelEnd)
        InvalidateSelection()
        EnsureCursorVisible()
        Dim Reverse = SelStart.Line > SelEnd.Line OrElse (SelStart.Line = SelEnd.Line And SelStart.Pos > SelEnd.Pos)
        Sel(If(Reverse, 1, 0)) = New Point(SelStart.Pos, SelStart.Line)
        Sel(If(Reverse, 0, 1)) = New Point(SelEnd.Pos, SelEnd.Line)
    End Sub

    Protected Overrides Function ProcessDialogKey(ByVal keyData As System.Windows.Forms.Keys) As Boolean
        Select Case keyData
            Case Windows.Forms.Keys.Left
                If SelStart <> SelEnd Then ClearSelection(True) : Return True
                MoveCursor(False, False, 0, -1)
                Return True
            Case Windows.Forms.Keys.Right
                If SelStart <> SelEnd Then ClearSelection(False) : Return True
                MoveCursor(False, False, 0, 1)
                Return True
            Case Windows.Forms.Keys.Up
                ClearSelection(True)
                MoveCursor(False, False, -1, 0)
                Return True
            Case Windows.Forms.Keys.Down
                ClearSelection(False)
                MoveCursor(False, False, 1, 0)
                Return True
            Case Windows.Forms.Keys.PageDown
                ClearSelection(False)
                TryVerticalScroll(VisibleLineCount * LineHeight)
                MoveCursor(False, False, VisibleLineCount, 0)
                Return True
            Case Windows.Forms.Keys.PageUp
                ClearSelection(True)
                TryVerticalScroll(-VisibleLineCount * LineHeight)
                MoveCursor(False, False, -VisibleLineCount, 0)
                Return True
            Case Windows.Forms.Keys.Left Or Keys.Control
                MoveCursor(False, False, 0, -1)
                Dim Fc = ActiveLine.Text(SelEnd.Pos)
                If Fc = " " Then Fc = "a"
                Do Until SelEnd.Line = 0 OrElse SelEnd.Pos = 0
                    MoveCursor(False, False, 0, -1)
                    If SelEnd.Pos = 0 Then Exit Do
                    Dim c = ActiveLine.Text(SelEnd.Pos)
                    If Not Line.IsBreak(Fc) And Line.IsBreak(c) Then MoveCursor(False, False, 0, 1) : Exit Do
                    If c = " " AndAlso Fc <> " " Then MoveCursor(False, False, 0, 1) : Exit Do
                    If Line.IsBreak(Fc) Then MoveCursor(False, False, 0, 1) : Exit Do
                Loop
                Return True
            Case Windows.Forms.Keys.Right Or Keys.Control
                If ActiveLine.Text.Length = SelEnd.Pos Then
                    If SelEnd.Line = Lines.Count - 1 Then Return True
                    MoveCursor(False, False, 0, 1)
                    If ActiveLine.Text.Length = SelEnd.Pos Then Return True
                End If
                Dim Fc = ActiveLine.Text(SelEnd.Pos)
                Dim IsWord = Not Line.IsBreak(Fc)
                Do
                    MoveCursor(False, False, 0, 1)
                    If Not IsWord Then Exit Do
                    If ActiveLine.Text.Length = SelEnd.Pos Then Return True
                    Dim c = ActiveLine.Text(SelEnd.Pos)
                    If IsWord And Line.IsBreak(c) Then Exit Do
                Loop
                If ActiveLine.Text.Length = SelEnd.Pos Then Return True
                Do
                    Dim c = ActiveLine.Text(SelEnd.Pos)
                    If c <> " " Then Exit Do
                    MoveCursor(False, False, 0, 1)
                Loop
                Return True
            Case Windows.Forms.Keys.Left Or Windows.Forms.Keys.Shift
                MoveCursor(True, False, 0, -1)
                Return True
            Case Windows.Forms.Keys.Right Or Windows.Forms.Keys.Shift
                MoveCursor(True, False, 0, 1)
                Return True
            Case Windows.Forms.Keys.Up Or Windows.Forms.Keys.Shift
                MoveCursor(True, False, -1, 0)
                Return True
            Case Windows.Forms.Keys.Down Or Windows.Forms.Keys.Shift
                MoveCursor(True, False, 1, 0)
                Return True
            Case Windows.Forms.Keys.PageUp Or Windows.Forms.Keys.Shift
                TryVerticalScroll(-VisibleLineCount * LineHeight)
                MoveCursor(True, False, -VisibleLineCount, 0)
                Return True
            Case Windows.Forms.Keys.PageDown Or Windows.Forms.Keys.Shift
                TryVerticalScroll(VisibleLineCount * LineHeight)
                MoveCursor(True, False, VisibleLineCount, 0)
                Return True
            Case Windows.Forms.Keys.Home
                ClearSelection(True)
                MoveCursor(False, True, -1, ActiveLine.GetHomePos(SelEnd.Pos))
                Return True
            Case Windows.Forms.Keys.End
                ClearSelection(True)
                MoveCursor(False, True, -1, Lines(SelStart.Line).Length)
                Return True
            Case Windows.Forms.Keys.Home Or Windows.Forms.Keys.Shift
                MoveCursor(True, True, -1, ActiveLine.GetHomePos(SelEnd.Pos))
                Return True
            Case Windows.Forms.Keys.End Or Windows.Forms.Keys.Shift
                MoveCursor(True, True, -1, ActiveLine.Length)
                Return True
            Case Windows.Forms.Keys.Control + Windows.Forms.Keys.Home
                ClearSelection(True)
                MoveCursor(False, True, 0, 0)
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.End
                ClearSelection(True)
                MoveCursor(False, True, Lines.Count - 1, Lines.Last.Length)
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.Home Or Windows.Forms.Keys.Shift
                MoveCursor(True, True, 0, 0)
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.End Or Windows.Forms.Keys.Shift
                MoveCursor(True, True, Lines.Count - 1, Lines.Last.Length)
                Return True
            Case Windows.Forms.Keys.Delete
                If SelStart = SelEnd Then MoveCursor(True, False, 0, 1)
                DeleteSelection()
                Return True
            Case Windows.Forms.Keys.Back
                If SelStart = SelEnd Then MoveCursor(True, False, 0, -1)
                DeleteSelection()
                Return True
            Case Windows.Forms.Keys.Return
                Lines.Insert(SelEnd.Line + 1, New Line(Mid(ActiveLine.Text, SelEnd.Pos + 1)))
                ActiveLine.Text = Mid(ActiveLine.Text, 1, SelEnd.Pos)
                IncreaseScrollArea(1)
                MoveCursor(False, True, SelEnd.Line + 1, 0)
                InvalidateLines(SelEnd.Line)
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.X, Windows.Forms.Keys.Shift Or Windows.Forms.Keys.Delete
                Cut()
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.C, Windows.Forms.Keys.Control Or Windows.Forms.Keys.Insert
                Copy()
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.V, Windows.Forms.Keys.Shift Or Windows.Forms.Keys.Insert
                Paste()
                Return True
            Case Windows.Forms.Keys.Control Or Windows.Forms.Keys.A
                SelectAll()
                Return True
        End Select
        Return MyBase.ProcessDialogKey(keyData)
    End Function

    Shared Function TextWidth(ByVal G As Graphics, ByVal Text As String, ByVal Font As Font) As Integer
        Static Items As New SortedList(Of String, Integer)
        Text = StrDup(Text.Length, "x")
        If Not Items.ContainsKey(Text) Then
            Items.Add(Text, G.MeasureText(Text, Font, New Size(100000, 100000), Windows.Forms.TextFormatFlags.NoPadding Or Windows.Forms.TextFormatFlags.NoPrefix).Width)
        End If
        Return Items(Text)
    End Function

    Protected Overrides Sub OnFontChanged(ByVal e As System.EventArgs)
        MyBase.OnFontChanged(e)
        LineHeight = Font.Height * 1
        VScroll.SmallChange = LineHeight
    End Sub

    Sub Open(ByVal File As String)
        Lines.Clear()
        For Each Item In Split(My.Computer.FileSystem.ReadAllText(File, System.Text.Encoding.Default), vbCrLf).ToList
            Lines.Add(New Line(Item))
        Next
        Invalidate()
        VirtualSize = New Size(10000, LineHeight * Lines.Count)
        VScroll.SmallChange = LineHeight
    End Sub

    Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
        MyBase.OnMouseDown(e)
        Dim P = FindLocation(e.X, e.Y)
        If ModifierKeys = Windows.Forms.Keys.Shift Then
            MoveCursor(True, True, P.Y, P.X)
        Else
            MoveCursor(False, True, P.Y, P.X)
        End If
    End Sub

    Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
        MyBase.OnMouseMove(e)
        If e.Button = Windows.Forms.MouseButtons.Left Then
            Dim P = FindLocation(e.X, e.Y)
            MoveCursor(True, True, P.Y, P.X)
        End If
    End Sub

    Private Function FindLocation(ByVal xc As Integer, ByVal yc As Integer) As Point
        xc -= ClientRectangle.Left
        If yc > ClientRectangle.Bottom Then yc = ClientRectangle.Bottom + 1
        If yc < 0 Then yc = -1
        Dim y = Int((yc + VScroll.Value - ClientRectangle.Top) / LineHeight)
        If y >= Lines.Count Then y = Lines.Count - 1
        If y < 0 Then y = 0
        Dim G = CreateGraphics()
        Dim l As New List(Of Integer)
        Dim w As New List(Of Integer)
        For i = 1 To Len(Lines(y).Text)
            Dim x = TextWidth(G, Mid(Lines(y).Text, 1, i - 1), Font) - HScroll.Value
            If x >= xc Then l.Add(i - 1) : w.Add(x) : If l.Count > 1 Then Exit For
        Next
        Dim xx As Integer = 0
        Select Case l.Count
            Case 0 : xx = Lines(y).Length
            Case 1 : xx = l.First
            Case 2 : xx = If(Math.Abs(w.First - xc) < Math.Abs(w.Last - xc), l.First, l.Last)
        End Select
        Return New Size(xx, y)
    End Function

    Overloads Sub Invalidate(ByVal i As Integer)
        Invalidate(LineRect(i))
    End Sub

    Private Sub DeleteSelection()
        If Sel(0) = Sel(1) Then Exit Sub
        Dim LinesToDelete = Sel(1).Y - Sel(0).Y
        Lines(Sel(0).Y).Text = Mid(Lines(Sel(0).Y).Text, 1, Sel(0).X) & Mid(Lines(Sel(1).Y).Text, Sel(1).X + 1)
        'If Sel(0).Y = Sel(1).Y Then Exit Sub
        Dim L = 0
        For i = Sel(1).Y To Sel(0).Y + 1 Step -1
            Lines.RemoveAt(i)
            L += LineHeight
        Next
        IncreaseScrollArea(-LinesToDelete)
        InvalidateLines(Sel(0).Y)
        MoveCursor(False, True, Sel(0).Y, Sel(0).X)
    End Sub

    Class Line

        Dim MustCalculate As Boolean
        Private _getHomePos As Integer

        Sub New(ByVal Text As String)
            Me.Text = Text
        End Sub

        Dim _Text As String
        Property Text As String
            Get
                Return _Text
            End Get
            Set(ByVal Value As String)
                _Text = Value
                MustCalculate = True
            End Set
        End Property

        ReadOnly Property Length As Integer
            Get
                Return Text.Length
            End Get
        End Property

        Dim Parts As New List(Of Part)

        ReadOnly Property GetHomePos(ByVal Pos As Integer) As Integer
            Get
                For i = 0 To Text.Length - 1
                    If Text(i) <> " " AndAlso i <> Pos Then Return i
                    If Text(i) <> " " Then Return 0
                Next
                Return 0
            End Get
        End Property

        Sub Draw(ByVal g As Graphics, ByVal R As Rectangle, ByVal Font As Font)
            If MustCalculate Then Recalculate(g, R, Font)
            For Each Part In Parts
                g.DrawText(Part.Text, Font, R, Part.Color, Windows.Forms.TextFormatFlags.NoPadding Or Windows.Forms.TextFormatFlags.NoPrefix Or TextFormatFlags.PreserveGraphicsTranslateTransform Or TextFormatFlags.VerticalCenter)
                R.Offset(Part.Offset, 0)
            Next
        End Sub

        Shared Function IsBreak(ByVal c As Char) As Boolean
            Select Case c
                Case " ", ",", "." : Return True
                Case "(", ")", "{", "}", ":", ";" : Return True
                Case "=", ">", "<" : Return True
                Case "*", "+", "-", "/" : Return True
                Case """", "'" : Return True
            End Select
            Return False
        End Function

        Class Part

            Public Text As String
            Public Color As Color
            Public Offset As Integer

            Sub New(ByVal Text As String, ByVal Color As Color)
                Me.Text = Text
                Me.Color = Color
            End Sub

        End Class

        Private Sub Recalculate(ByVal g As Graphics, ByVal R As Rectangle, ByVal Font As Font)
            Dim c As Integer = 0
            Dim w As String = ""
            Dim InString As Boolean
            Dim InRemark As Boolean
            Dim t = Text & " "
            Parts.Clear()
            Do
                If IsBreak(t(c)) Then
                    If InRemark AndAlso c = t.Length - 1 Then
                        w &= t(c)
                        Parts.Add(New Part(w, Color.Green))
                    ElseIf InRemark Then
                        w &= t(c)
                    ElseIf InString AndAlso (t(c) = """" OrElse c = t.Length - 1) Then
                        w &= t(c)
                        Parts.Add(New Part(w, Color.DarkRed))
                        w = ""
                        InString = False
                    ElseIf InString Then
                        w &= t(c)
                    Else
                        If CodeBox.Keywords.Contains(w.ToLower) Then
                            Parts.Add(New Part(w, Color.Blue))
                        Else
                            Parts.Add(New Part(w, Color.Black))
                        End If
                        w = ""
                        If Not InString And t(c) = "'" Then
                            InRemark = True
                            w = t(c)
                        ElseIf t(c) = """" Then
                            InString = True
                            w = t(c)
                        Else
                            Parts.Add(New Part(t(c), Color.SlateBlue))
                        End If
                    End If
                Else
                    w &= t(c)
                End If
                c += 1
                If c = t.Length Then Exit Do
            Loop
            For Each Part In Parts
                Part.Offset = CodeBox.TextWidth(g, Part.Text, Font)
            Next
            MustCalculate = False
        End Sub

    End Class

    Class CursorPoint
        Property Parent As CodeBox
        Property Line As Integer
        Property Pos As Integer

        ReadOnly Property Lines As List(Of Line)
            Get
                Return Parent.Lines
            End Get
        End Property

        Sub InvalidateLine()
            Parent.Invalidate(Line)
        End Sub

        Shared Operator >(ByVal c1 As CursorPoint, ByVal c2 As CursorPoint) As Boolean
            If c1.Line > c2.Line Then Return True
            If c1.Line < c2.Line Then Return False
            If c1.Pos > c2.Pos Then Return True
            Return False
        End Operator

        Shared Operator <(ByVal c1 As CursorPoint, ByVal c2 As CursorPoint) As Boolean
            If c2.Line > c1.Line Then Return True
            If c2.Line < c1.Line Then Return False
            If c2.Pos > c1.Pos Then Return True
            Return False
        End Operator

        Shared Operator =(ByVal c1 As CursorPoint, ByVal c2 As CursorPoint) As Boolean
            Return c2.Line = c1.Line AndAlso c2.Pos = c1.Pos
        End Operator

        Shared Operator <>(ByVal c1 As CursorPoint, ByVal c2 As CursorPoint) As Boolean
            Return Not c1 = c2
        End Operator

        Sub [Set](ByVal c As CursorPoint)
            If Line = c.Line And Pos = c.Pos Then Return
            Pos = c.Pos
            Line = c.Line
        End Sub

        Private Sub MovePos(ByVal n As Integer)
            Pos += n
            If Pos < 0 Then
                If Line = 0 Then Pos = 0 : Return
                Line -= 1
                Pos = Lines(Line).Length
            ElseIf Pos > Lines(Line).Length Then
                If Line = Lines.Count - 1 Then Pos = Lines(Line).Length : Return
                Line += 1
                Pos = 0
            End If
        End Sub

        Private Sub MoveLine(ByVal n As Integer)
            Line += n
            If Line < 0 Then Line = 0
            If Line >= Lines.Count Then Line = Lines.Count - 1
        End Sub

        Sub MoveCursor(ByVal Absolute As Boolean, ByVal Line As Integer, ByVal Pos As Integer)
            If Absolute Then
                If Line >= 0 Then Me.Line = Line
                If Pos >= 0 Then Me.Pos = Pos
            Else
                If Line <> 0 Then MoveLine(Line)
                If Pos <> 0 Then MovePos(Pos)
            End If
            If Me.Pos > Lines(Me.Line).Length Then Me.Pos = Lines(Me.Line).Length
        End Sub

        Protected Overrides Sub Finalize()
            MyBase.Finalize()
        End Sub

    End Class

    Private Sub ClearSelection(ByVal ToLeft As Boolean)
        InvalidateSelection()
        If ToLeft Then
            If SelStart > SelEnd Then SelStart.Set(SelEnd) Else SelEnd.Set(SelStart)
        Else
            If SelStart < SelEnd Then SelStart.Set(SelEnd) Else SelEnd.Set(SelStart)
        End If
    End Sub

    Private Sub InvalidateSelection()
        Static LastFullDrawn As New List(Of Integer)
        Dim FullDrawn As New List(Of Integer)
        Dim F1 = Math.Min(SelStart.Line, SelEnd.Line)
        Dim F2 = Math.Max(SelStart.Line, SelEnd.Line)
        Invalidate(LineRect(F1))
        Invalidate(LineRect(F2))
        For i = Math.Max(TopLine, F1 + 1) To Math.Min(BottomLine, F2 - 1)
            If Not LastFullDrawn.Contains(i) Then Invalidate(LineRect(i))
            FullDrawn.Add(i)
        Next
        For Each Item In (From i In LastFullDrawn Where Not FullDrawn.Contains(i))
            Invalidate(LineRect(Item))
        Next
        LastFullDrawn = FullDrawn
    End Sub

    Private Sub InvalidateLines(ByVal First As Integer)
        For i = Math.Max(First, TopLine) To BottomLine
            Invalidate(LineRect(i))
        Next
    End Sub

    Private Sub IncreaseScrollArea(ByVal LineCount As Integer)
        VirtualSize = New Size(VirtualSize.Width, VirtualSize.Height + LineHeight * LineCount)
    End Sub

    Sub Copy()
        My.Computer.Clipboard.SetText(SelectedText)
    End Sub

    Sub Cut()
        Copy()
        DeleteSelection()
    End Sub

    Sub Paste()
        DeleteSelection()
        Dim Items = Split(My.Computer.Clipboard.GetText, vbCrLf)
        If UBound(Items) = 0 Then
            Lines(SelStart.Line).Text = Lines(SelStart.Line).Text.Insert(SelStart.Pos, Items.First)
            MoveCursor(False, False, 0, Len(Text))
            Exit Sub
        End If
        Dim Before = Mid(Lines(SelStart.Line).Text, 1, SelStart.Pos)
        Dim After = Mid(Lines(SelStart.Line).Text, SelStart.Pos + 1)
        Dim LinesToInsert As New List(Of Line)
        Lines(SelStart.Line).Text = Before & Items.First
        For j = 1 To UBound(Items) - 1
            LinesToInsert.Add(New Line(Items(j)))
        Next
        LinesToInsert.Add(New Line(Items.Last & After))
        Lines.InsertRange(SelStart.Line + 1, LinesToInsert)
        IncreaseScrollArea(Items.Count - 1)
        InvalidateLines(SelStart.Line)
        MoveCursor(False, True, SelStart.Line + LinesToInsert.Count, Items.Last.Length)
    End Sub

    ReadOnly Property SelectedText As String
        Get
            Dim SB As New StringBuilder
            If Sel(0).Y = Sel(1).Y Then Return Mid(Lines(Sel(0).Y).Text, Sel(0).X + 1, Sel(1).X - Sel(0).X)
            SB.AppendLine(Mid(Lines(Sel(0).Y).Text, Sel(0).X + 1))
            For i = Sel(0).Y + 1 To Sel(1).Y - 1
                SB.AppendLine(Lines(i).Text)
            Next
            SB.Append(Mid(Lines(Sel(1).Y).Text, 1, Sel(1).X))
            Return SB.ToString
        End Get
    End Property

    Private Sub SelectAll()
        MoveCursor(False, True, 0, 0)
        MoveCursor(True, True, Lines.Count - 1, Lines.Last.Length)
    End Sub

    Sub Vb()
        Keywords.AddRange(New String() {
            "public", "class", "as", "new", "dim", "ınteger", "withevents", "of", "string", "with", "true", "false",
            "byval", "property", "get", "set", "not", "mid", "else", "each", "ın",
            "ıf", "then", "elseıf", "return", "end", "boolean", "private",
            "exit", "do", "loop", "ımports", "ınherits", "shared",
            "select", "case", "function", "sub", "or", "and", "orelse", "andalso",
            "protected", "overrides", "mybase", "readonly",
            "for", "to", "next", "len"})
    End Sub
End Class

